package com.wangge.concurrentTest;

import java.io.IOException;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 客户,起服务时间未定
 */
class Customer {

  private final int serviceTime;

  public Customer(int serviceTime) {
    this.serviceTime = serviceTime;
  }

  public int getServiceTime() {
    return serviceTime;
  }

  @Override
  public String toString() {
    return "Customer [" + serviceTime + "]";
  }

}

/**
 * 客户队列
 * 
 */
class CustomerLine extends ArrayBlockingQueue<Customer> {

  private static final long serialVersionUID = 8882600128832732764L;

  public CustomerLine(int arg0) {
    super(arg0);
  }

  public String toString() {
    if (this.size() == 0)
      return "[Empty]";
    StringBuilder result = new StringBuilder();
    for (Customer customer : this)
      result.append(customer);
    return result.toString();
  }
}

/**
 * 客户生成器
 * 
 */
class CustomerGenerator implements Runnable {
  private static Random random = new Random(47);
  private CustomerLine customers;

  public CustomerGenerator(CustomerLine customers) {
    super();
    this.customers = customers;
  }

  @Override
  public void run() {
    try {
      while (!Thread.interrupted()) {
        TimeUnit.MICROSECONDS.sleep(random.nextInt(300));
        customers.put(new Customer(random.nextInt(1000)));
      }
    } catch (InterruptedException e) {
      System.out.println("CustomerGenerator interrupted");
    }
    System.out.println("CustomerGenerator terminating");
  }
}

/** 
 * 营业员,两个功能,1.服务顾客,2.做其他事情.
  */  
class Teller implements Runnable, Comparable<Teller> {
  private static int count = 0;
  private final int id = count++;
  private int customerServed = 0;
  private CustomerLine que;
  private boolean servingCustomerLine = true;

  public Teller(CustomerLine que) {
    super();
    this.que = que;
  }

  @Override
  public void run() {
    try {
      while (!Thread.interrupted()) {
        Customer customer = que.take();
        Thread.currentThread();
        Thread.sleep(customer.getServiceTime());
        synchronized (this) {
          customerServed++;
          while (!servingCustomerLine)
            wait();
        }
      }
    } catch (InterruptedException e) {
      System.out.println(this + "interrupted");
    }
    System.out.println(this + "terminating");
  }

  public synchronized void doSomethingElse() {
    customerServed = 0;
    servingCustomerLine = false;
  }

  public synchronized void serveCustomerLine() {
    if (servingCustomerLine) {
      throw new RuntimeException("alread serving: " + this);
    } else {
      servingCustomerLine = true;
      notifyAll();
    }
  }

  public String toString() {
    return "Teller" + id;
  }

  public String shortString() {
    return "T " + id;
  }

  public int getCustomerServed() {
    return customerServed;
  }

  public void setCustomerServed(int customerServed) {
    this.customerServed = customerServed;
  }

  @Override
  public synchronized int compareTo(Teller other) {

    return this.getCustomerServed() == other.getCustomerServed() ? 0
        : this.getCustomerServed() > other.getCustomerServed() ? -1 : 1;
  }
}

class TellerManager implements Runnable {
  private PriorityQueue<Teller> workQueue = new PriorityQueue<Teller>();
  private Queue<Teller> doSomethingQueue = new LinkedList<Teller>();
  private ExecutorService exec;

  private CustomerLine customerLine;
  private int adjustmentPeriod;

  public TellerManager(ExecutorService exec, CustomerLine customerLine, int adjustmentPeriod) {
    this.exec = exec;
    this.customerLine = customerLine;
    this.adjustmentPeriod = adjustmentPeriod;
    Teller teller = new Teller(customerLine);
    exec.execute(teller);
    workQueue.add(teller);
  }

  @Override
  public void run() {
    try {
      while (!Thread.interrupted()) {
        TimeUnit.MICROSECONDS.sleep(adjustmentPeriod);
        adjustTellerNumber();
        System.out.println(customerLine + "{");
        workQueue.stream().forEach(teller -> System.out.println(teller.shortString()));
        System.out.println("}");
      }
    } catch (Exception e) {
      System.out.println(this + "interruted");
    }
    System.out.println(this + " terminating");
  }

  public void adjustTellerNumber() {
    if (customerLine.size() / workQueue.size() > 2) {
      if (doSomethingQueue.size() > 0) {
        Teller teller = doSomethingQueue.remove();
        teller.serveCustomerLine();
        workQueue.offer(teller);
        return;
      }
      // 否则则重新创建一个营业员
      Teller teller = new Teller(customerLine);
      exec.execute(teller);
      workQueue.add(teller);
      return;
    }
    if (customerLine.size() / workQueue.size() < 2 && workQueue.size() > 1) {
      reassignOneTeller();
    }
    // 如果没有客户,就保持一个服务
    if (customerLine.size() == 0) {
      while (workQueue.size() > 1)
        reassignOneTeller();
    }
  }

  private void reassignOneTeller() {
    Teller teller = workQueue.poll();
    teller.doSomethingElse();
    doSomethingQueue.offer(teller);

  }

}

public class TellerSimulation {
  private static int customerSize = 50;
  private static int ADJUSTMENT_PERIOD = 1000;

  public static void main(String[] args) throws IOException {
    CustomerLine customers = new CustomerLine(customerSize);
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.execute(new CustomerGenerator(customers));
    exec.execute(new TellerManager(exec, customers, ADJUSTMENT_PERIOD));
    System.out.println("print 'enter' to quit!");
    System.in.read();
    System.exit(0);

  }
}
